/*
  nuts_bolts.c - Shared functions

  Part of grblHAL

  Copyright (c) 2017-2024 Terje Io
  Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
  Copyright (c) 2009-2011 Simen Svale Skogsrud

  grblHAL is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  grblHAL is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
*/

#include <math.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>

#include "hal.h"
#include "protocol.h"
#include "state_machine.h"
#include "nuts_bolts.h"

#ifndef DWELL_TIME_STEP
#define DWELL_TIME_STEP 50 // Integer (1-255) (milliseconds)
#endif

#define MAX_PRECISION 10

// 这个数组用于接收外面的数据和将处理好的数据传递出去
// 有好几个函数使用了它,也就是说,在多线程的时候这是不安全的
static char buf[STRLEN_COORDVALUE + 1];

static const float froundvalues[MAX_PRECISION + 1] =
{
    0.5f,                // 0
    0.05f,               // 1
    0.005f,              // 2
    0.0005f,             // 3
    0.00005f,            // 4
    0.000005f,           // 5
    0.0000005f,          // 6
    0.00000005f,         // 7
    0.000000005f,        // 8
    0.0000000005f,       // 9
    0.00000000005f       // 10
};

#if N_AXIS > 6 && defined(AXIS_REMAP_ABC2UVW)
#error "Illegal remapping of ABC axes!"
#endif

char const *const axis_letter[N_AXIS] = {
    "X",
    "Y",
    "Z"
#if N_AXIS > 3
  #if !AXIS_REMAP_ABC2UVW
    ,"A"
  #else
    ,"U"
  #endif
#endif
#if N_AXIS > 4
  #if !AXIS_REMAP_ABC2UVW
    ,"B"
  #else
    ,"V"
  #endif
#endif
#if N_AXIS > 5
 #if !AXIS_REMAP_ABC2UVW
    ,"C"
  #else
    ,"W"
  #endif
#endif
#if N_AXIS > 6
    ,"U"
#endif
#if N_AXIS > 7
    ,"V"
#endif
};

// Converts an uint32 variable to string.
// 整数转换成字符串
char *uitoa (uint32_t n)
{
    char *bptr = buf + sizeof(buf);

    *--bptr = '\0';

    if (n == 0)
        *--bptr = '0';
    else while (n) {
        *--bptr = '0' + (n % 10);
        n /= 10;
    }

    return bptr;
}

// Convert float to string by immediately converting to integers.
// Number of decimal places, which are tracked by a counter, must be set by the user.
// The integers is then efficiently converted to a string.
/**
 * @brief 将浮点数转换为字符串，保留指定的小数位数。
 * 
 * 该函数将一个浮点数转换为其字符串表示形式，保留指定的小数位数，并通过查表方式进行四舍五入。
 * 它支持负数、小数位的处理，并使用整数运算优化性能。
 * 
 * @param n 要转换的浮点数。
 * @param decimal_places 要保留的小数位数。
 * @return 指向缓冲区中浮点数字符串的指针。
 */
char *ftoa (float n, uint8_t decimal_places)
{
    bool isNegative;                // 标记是否为负数
    char *bptr = buf + sizeof(buf);  // 指向缓冲区末尾（用于倒序填充字符）
    *--bptr = '\0';  // 字符串以空字符结尾（初始化为空字符串）
    // 如果是负数，将其转为正数，并设置负数标志
    if ((isNegative = n < 0.0f))  // 检查数字是否为负
        n = -n;  // 如果是负数，将其转换为正数进行后续处理
    // 通过查表实现四舍五入，根据小数位数调整浮点数
    n += froundvalues[decimal_places];  // 查表进行四舍五入
    uint32_t a = (uint32_t)n;  // 提取浮点数的整数部分
    // 如果需要小数部分，则处理小数
    if (decimal_places) {
        // 减去整数部分，获取浮动的十进制小数部分
        n -= (float)a;
        uint_fast8_t decimals = decimal_places;
        // 针对CNC应用的优化，将小数部分快速转换为整数
        while (decimals >= 2) {  // 每次处理2个小数位
            n *= 100.0f;  // 将小数部分放大100倍
            decimals -= 2;  // 减少处理的小数位数
        }
        if (decimals)  // 如果还有1位小数，进行单独处理
            n *= 10.0f;
        uint32_t b = (uint32_t)n;  // 获取小数部分的整数部分
        // 将小数部分的数字逐位转换成字符，并存储在缓冲区
        while(decimal_places--) {
            if(b) {
                *--bptr = (b % 10) + '0';  // 提取当前位的数字并存入缓冲区
                b /= 10;  // 去掉当前位数字
            } else
                *--bptr = '0';  // 如果没有更多数字，填充0
        }
    }
    *--bptr = '.';  // 始终添加小数点（即使没有小数部分）
    // 如果整数部分为0，则填充'0'
    if(a == 0)
        *--bptr = '0';
    else while(a) {
        *--bptr = (a % 10) + '0';  // 提取整数部分的数字并存储
        a /= 10;  // 去掉当前位数字
    }
    // 如果是负数，添加负号
    if(isNegative)
        *--bptr = '-';
    return bptr;  // 返回指向字符串开头的指针
}


// Trim trailing zeros and possibly decimal point
// 去除浮点数表示中的尾随零以及可能多余的小数点
char *trim_float (char *s)
{
    // 使用 strchr 查找字符串中的小数点字符（'.'）
    // 如果字符串中包含小数点，则继续处理
    if (strchr(s, '.')) {
        // 找到字符串结束符（'\0'）的位置，s2 指向字符串的最后一个字符
        char *s2 = strchr(s, '\0') - 1;
        // 从字符串的尾部开始，去除尾随的零
        while (*s2 == '0') // 如果字符是 '0'
            *s2-- = '\0';   // 用 '\0' 替换该字符，并将指针向前移动
        // 如果字符串的最后一个字符是小数点 ('.')，则去除小数点
        if (*s2 == '.')
            *s2 = '\0';  // 用 '\0' 替换小数点，删除多余的小数点
    }
    // 返回处理后的字符串
    return s;
}


// Extracts an unsigned integer value from a string.
status_code_t read_uint (char *line, uint_fast8_t *char_counter, uint32_t *uint_ptr)
{
    // 定义指针，指向当前字符位置，开始处理
    char *ptr = line + *char_counter;
    int_fast8_t exp = 0; // 指数，用于处理溢出数字
    uint_fast8_t ndigit = 0, c; // ndigit 记录数字的位数，c 当前字符
    uint32_t intval = 0; // 用于存储转换后的整数值
    bool isdecimal = false, ok = false; // isdecimal 标记是否遇到小数点，ok 用于检查是否读取到有效数字

    // 取出第一个字符并递增指针
    c = *ptr++;
    // 如果是负号，返回错误代码：负值不支持
    if (c == '-')
        return Status_NegativeValue;
    // 如果是正号，跳过正号字符
    if (c == '+')
        c = *ptr++;
    // 提取数字并存储到整数值中，如果是小数则跟踪小数点
    while(c) {
        c -= '0'; // 将字符转换为对应的数字（'0' -> 0, '1' -> 1, ..., '9' -> 9）

        // 如果是有效的数字
        if (c <= 9) {
            ok = true; // 设置为有效
            if(!isdecimal && (c != 0 || intval)) // 如果不是小数并且遇到非零数字或整数值不为零
                ndigit++; // 统计有效数字的个数
            if (isdecimal && c != 0) // 如果是小数点后面的数字并且不为零，则返回错误
                return Status_GcodeCommandValueNotInteger;
            // 如果数字位数不超过 9 且整数值不溢出，则继续构建整数
            if ((ndigit <= 9 || c <= 4) && intval <= 429496729) {
                // intval = intval * 10 + c，这里采用位运算优化，`intval = ((intval << 2) + intval) << 1` 即相当于乘以 10
                // 这个值得记住
                intval = (((intval << 2) + intval) << 1) + c; 
            } else if (!isdecimal)
                exp++;  // 如果溢出且不是小数，增加指数
        } 
        // 如果遇到小数点并且没有遇到过小数点
        else if (c == (uint_fast8_t)('.' - '0') && !isdecimal)
            isdecimal = true; // 标记小数点
        else
            break; // 其他字符则跳出循环

        // 继续指向下一个字符
        c = *ptr++;
    }
    // 如果没有读取到任何有效的数字，返回格式错误
    if (!ok)
        return Status_BadNumberFormat;
    // 将解析得到的整数值赋给输出参数
    *uint_ptr = intval;
    // 更新字符计数器，指向下一个位置（即当前指针减去初始位置）
    *char_counter = ptr - line - 1;
    // 返回成功状态
    return Status_OK;
}


// Extracts a floating point value from a string. The following code is based loosely on
// the avr-libc strtod() function by Michael Stumpf and Dmitry Xmelkov and many freely
// available conversion method examples, but has been highly optimized for Grbl. For known
// CNC applications, the typical decimal value is expected to be in the range of E0 to E-4.
// Scientific notation is officially not supported by g-code, and the 'E' character may
// be a g-code word on some CNC systems. So, 'E' notation will not be recognized.
// NOTE: Thanks to Radu-Eosif Mihailescu for identifying the issues with using strtod().
// 从字符串中提取浮点数值。
// 该代码基于avr-libc的strtod()函数（由Michael Stumpf和Dmitry Xmelkov编写）和许多自由可用的转换方法示例，但经过高度优化以适应Grbl。
// 对于已知的CNC应用，通常期望的十进制值在E0到E-4之间。
// 科学计数法在g-code中并不正式支持，'E'字符在某些CNC系统中可能作为g-code单词出现。
// 因此，本函数不支持科学计数法（'E'不被识别）。
// 注意：感谢Radu-Eosif Mihailescu指出了使用strtod()时的问题。
// 当 line = "123.456", *char_counter = 1的时候,ptr = "23.456"
// 当处理完成之后, *char_counter = strlen("123.456"),float_ptr = 23.456
// 遇到非数字或者字符串结束符号的时候会停止提取
bool read_float (char *line, uint_fast8_t *char_counter, float *float_ptr)
{
    char *ptr = line + *char_counter;                // 指向当前字符的位置
    int_fast8_t exp = 0;                             // 指数部分，初始化为0
    uint_fast8_t ndigit = 0, c;                      // ndigit 用于计数字符串中的数字位数，c用于存储当前字符
    uint32_t intval = 0;                             // 存储整数部分的值
    bool isnegative, isdecimal = false, ok = false;  // 标志位：是否负数、是否是小数点、是否成功解析
    // 获取第一个字符并将指针向前移动。假设字符串中没有空格。
    c = *ptr++;
    // 处理符号字符（如果是负数，记录符号并跳过符号字符）
    if ((isnegative = (c == '-')) || c == '+')
        c = *ptr++;
    // 从字符串中提取数字，将其存入整数部分，跟踪小数点的指数位置。
    while( c ) {
        c -= '0';       // 将字符转换为对应的数字（'0' -> 0, '1' -> 1, ...）
        if (c <= 9) {   // 确保是数字
            ok = true;  // 标记成功解析
            if(c != 0 || intval)
                ndigit++;  // 统计非零的数字位数
            if (ndigit <= MAX_INT_DIGITS) {  // 如果整数部分的位数不超过最大位数,这里是说保留有效数字
                if (isdecimal)               // 如果是小数部分，调整指数
                    exp--;                   // 每遇到一位小数，指数减1
                intval = (((intval << 2) + intval) << 1) + c;     // 计算整数部分，intval = intval * 10 + c
            } else if (!isdecimal)
                exp++;                       // 如果位数溢出且未遇到小数点，增加指数来“丢弃”溢出部分
        } else if (c == (uint_fast8_t)('.' - '0') && !isdecimal)  // 如果遇到小数点且尚未处理小数点
            isdecimal = true;                // 标记已处理小数点
        else
            break;                           // 如果是非数字字符或其他无效字符，退出循环
        c = *ptr++;                          // 移动到下一个字符
    }
    // 如果没有解析到任何有效的数字，返回false
    if (!ok)
        return false;
    // 将整数部分转换为浮点数
    // 在这里小数还是整数的形式出现
    float fval = (float)intval;
    // 应用小数点，最多进行两次浮点乘法操作，适应预期的E0到E-4范围
    if (fval != 0.0f) {
        // 处理小数点前的指数（小于-2时，逐步除以100）
        while (exp <= -2) {
            fval *= 0.01f;  // 除以100
            exp += 2;       // 调整指数
        }
        // 根据指数调整浮点数的大小
        if (exp < 0)
            fval *= 0.1f;       // 如果指数为负，乘以0.1
        else if (exp > 0)       //前面如果有效数字超出允许的位数,那么超出的部分直接填0 假设有效数字是2,输入1234,那会变成1200(浮点数有存储误差)
            do {
                fval *= 10.0f;  // 如果指数为正，乘以10
            } while (--exp > 0);
    }
    // 将浮点数的值赋给输出参数，并根据符号进行调整
    *float_ptr = isnegative ? - fval : fval;
    // 更新char_counter，使其指向下一个未处理的字符
    *char_counter = ptr - line - 1;

    return true;  // 返回成功
}

// Returns true if float value is a whole number (integer)
// 判断给定的浮点数是否是整数（即是否是一个“整”值）
// 如果浮点数值是整数，返回 true；否则返回 false
bool isintf (float value)
{
    // 1. 判断输入值是否是一个有效的数字（非NaN）
    //    使用 isnanf 函数检查给定的浮点值是否为 "非数字"（NaN，Not-a-Number）
    //    如果是NaN，返回false
    // 2. 使用 fabsf 函数计算 value 与 truncf(value) 的差的绝对值是否小于 0.001f
    //    truncf(value) 会将浮点数值截断为整数部分，不进行四舍五入。
    //    通过比较浮动数值与截断后的整数部分之间的差异是否小于一个非常小的阈值（0.001f）来判断是否接近一个整数。
    return !isnanf(value) && fabsf(value - truncf(value)) < 0.001f;
}


// Non-blocking delay function used for general operation and suspend features.
// 非阻塞延迟函数，用于一般操作和暂停特性
// 参数：
// - seconds: 延迟的时间（秒）
// - mode: 延迟模式，指定延迟类型（如 DWELL 或 系统挂起）
// 返回：
// - 如果延迟过程没有被中断，返回 true；如果在延迟过程中发生了中断（如安全门打开），则返回 false。
bool delay_sec (float seconds, delaymode_t mode)
{
    bool ok = true;  // 用于标记延迟是否成功

    // 计算需要执行的DWELL_TIME_STEP次数，ceilf确保即使是小数也向上取整
    uint_fast16_t i = (uint_fast16_t)ceilf((1000.0f / DWELL_TIME_STEP) * seconds) + 1;

    // 执行延迟操作，直到延迟时间结束或者发生中断
    while(--i && ok) {
        if(mode == DelayMode_Dwell)  // 如果是DWELL模式，执行实时协议命令
            ok = protocol_execute_realtime();
        else    // 如果是系统挂起模式，执行系统实时命令，并检查安全门是否重新打开
            ok = protocol_exec_rt_system() && !state_door_reopened();  // 如果安全门重新打开，则退出

        if(ok)  // 如果没有发生中断，继续按DWELL_TIME_STEP的步长延迟
            hal.delay_ms(DWELL_TIME_STEP, NULL);  // 延迟一个DWELL_TIME_STEP的时间
    }

    return ok;  // 返回延迟是否成功
}


// 比如从 (1,1,1) 移动到 (4,5,6) 那么 vector[0] = 4 - 1; vector[1] = 5 - 1; vector[2] = 6 - 1
// 也就是每个轴在个方向的位移
float convert_delta_vector_to_unit_vector(float *vector)
{
    uint_fast8_t idx = N_AXIS;
    float magnitude = 0.0f, inv_magnitude;

    do {// 做合成矢量开根号前的运算
        if (vector[--idx] != 0.0f)
            magnitude += vector[idx] * vector[idx];
    } while(idx); //x^2 + y^2 + z^2 + ...... + w^2

    idx = N_AXIS;
    magnitude = sqrtf(magnitude);     // 求得向量模长
    inv_magnitude = 1.0f / magnitude; // 向量模长的倒数

    do {
        vector[--idx] *= inv_magnitude;// 分量 / 模长 比如 x / sqrt( x^2 + y^2 + z^2 )
    } while(idx);
    // 这样做完以后 vector[--idx] 存储的分量的模长就是1(单位向量)
    return magnitude;// 模长
}

// parse ISO8601 datetime: YYYY-MM-DDTHH:MM:SSZxxx
// 解析 ISO8601 格式的日期时间字符串：YYYY-MM-DDTHH:MM:SSZxxx
// 这个函数不考虑时区偏移
struct tm *get_datetime (const char *s)
{
    static struct tm dt;  // 定义并初始化一个结构体，用于存储解析后的日期时间
    PROGMEM static const uint8_t mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 每个月的天数

    char *s1 = (char *)s, c;  // 初始化指针s1指向输入字符串s，c用于存储当前字符
    uint_fast16_t idx = 0, value = 0;  // idx用于标记当前解析的字段，value用于存储当前解析的值

    memset(&dt, 0, sizeof(struct tm));  // 清空结构体dt，初始化为0
    dt.tm_year = dt.tm_mon = dt.tm_mday = dt.tm_hour = dt.tm_min = dt.tm_sec = -1;  // 初始化时间字段为-1，表示尚未解析
    do {
        c = *s1++;  // 获取下一个字符并移动指针
        if(isdigit(c))  // 如果字符是数字，则累加到value中
            value = (value * 10) + c - '0';  // 将字符转换为数字并累计

        else if(!(c == '-' || c == ':' || c == 'T' || c == 'Z' || c == '\0'))  // 如果字符不是'-'、':'、'T'、'Z'或者字符串结束符，则退出循环
            break;
        else {  // 如果字符是'-'、':'、'T'、'Z'或字符串结束符，表示一个时间字段的结束
            switch(idx) {  // 根据idx值判断当前是解析哪一部分
                case 0:  // 解析年份
                    if(c == '-' && value >= 1970 && value <= 2099)  // 年份必须在1970到2099之间
                        dt.tm_year = value - 1900;  // tm_year存储的是从1900年开始的年份，因此需要减去1900
                    break;
                case 1:  // 解析月份
                    if(c == '-' && value >= 1 && value <= 12)  // 月份必须在1到12之间
                        dt.tm_mon = value - 1;  // tm_mon存储的是0-11的月份，因此减去1
                    break;
                case 2:  // 解析日（天）
                    if(c == 'T' && value >= 1 && value <= (mdays[dt.tm_mon >= 0 ? dt.tm_mon : 0] + (dt.tm_mon == 1 && dt.tm_year != 100 && (dt.tm_year % 4) == 0 ? 1 : 0)))  // 根据月份和闰年判断天数
                        dt.tm_mday = value;  // 存储天数
                    break;
                case 3:  // 解析小时
                    if(c == ':' && value <= 23)  // 小时必须在0到23之间
                        dt.tm_hour = value;  // 存储小时
                    break;
                case 4:  // 解析分钟
                    if(c == ':' && value <= 59)  // 分钟必须在0到59之间
                        dt.tm_min = value;  // 存储分钟
                    break;

                case 5:  // 解析秒
                    if((c == 'Z' || c == '\0') && value <= 59)  // 秒数必须在0到59之间，且可以是'Z'或字符串结束符
                        dt.tm_sec = value;  // 存储秒数
                    break;
            }
            idx++;  // 解析下一个字段
            value = 0;  // 清空当前值，准备解析下一个字段
        }
    } while(c);  // 循环直到字符串结束
    // 如果年份、月份、天、小时、分钟、秒有任何一个字段有效，则返回dt，否则返回NULL
    return (dt.tm_year | dt.tm_mon | dt.tm_mday | dt.tm_hour | dt.tm_min | dt.tm_sec) > 0 ? &dt : NULL;
}

// calculate checksum byte for data
uint8_t calc_checksum (uint8_t *data, uint32_t size) 
{
    uint8_t checksum = 0;
    while(size--) {
        /* checksum = (checksum << 1) | (checksum >> 7);这种操作的效果是使得输入数据的顺序对校验和的计算结果有影响。
        例如，若某两个字节的数据顺序交换，其对应的校验和结果也会变化。这增加了校验的灵敏度，能够更好地捕捉到数据的变化。 */
        checksum = (checksum << 1) | (checksum >> 7);
        checksum += *(data++);
    }

    return checksum;
}

// Remove spaces from and convert string to uppercase (in situ)
// 删除空格并把小写字母变成大写字母
char *strcaps (char *s)
{
    char c, *s1 = s, *s2 = s;

    do {
        c = *s1++;
        if(c != ' ')
            *s2++ = CAPS(c);
    } while(c);

    if(s1 != s2) // 针对末尾是空格的情况做处理,把空格替换成 '\0'
        *s2 = '\0';

    return s;
}

void dummy_handler (void)
{
    // NOOP
}
